package com.lokyiu.tutorial.java.generics;

import java.util.ArrayList;

public class Generics {
	/**
	 * This method demonstrates a collection that does not use Generics.
	 * As you can see, Computer is unrelated to Apple, Pear or Fruit but
	 * it can still be added.
	 * 
	 * By default Eclipse will give you warnings (the yellow underscore)
	 * when a collection does not use Generics as this is not recommended.
	 * @return
	 */
	public ArrayList getFruits1() {
		ArrayList fruits = new ArrayList();
		fruits.add(new Apple());
		fruits.add(new Pear());
		fruits.add(new Fruit());
		fruits.add(new Computer());
		fruits.add(new BananaCake());
		return fruits;
	}
	
	/**
	 * This method introduces an ArrayList that is defined to contain
	 * objects of type Fruit.  As Apple and Pear objects are subclasses of Fruit,
	 * they are also allowed.  Computer is not a subclass of Fruit so it is not allowed, neither
	 * is BananaCake.
	 * 
	 * Notice that ArrayList<Fruit> can be returned even though the method is only defined to return
	 * the Generics-less ArrayList.  This means that the code that uses this method can 
	 * no longer assume that this list contains only objects of type Fruit.
	 * @return
	 */
	public ArrayList getFruits2_1() {
		ArrayList<Fruit> fruits = new ArrayList<Fruit>();
		fruits.add(new Apple());
		fruits.add(new Braeburn());
		fruits.add(new Pear());
		fruits.add(new Fruit());
		//Uncomment the following lines to see the error
		//fruits.add(new Computer());
		//fruits.add(new BananaCake());
		return fruits;
	}
	
	/**
	 * This method is essentially the same as getFruits2_1 except that
	 * the method is defined to return an ArrayList that only contains Fruit.
	 * @return
	 */
	public ArrayList<Fruit> getFruits2_2() {
		ArrayList<Fruit> fruits = new ArrayList<Fruit>();
		fruits.add(new Apple());
		fruits.add(new Braeburn());
		fruits.add(new Pear());
		fruits.add(new Fruit());
		//Uncomment the following lines to see the error
		//fruits.add(new Computer());
		//fruits.add(new BananaCake());
		return fruits;
	}
	
	/**
	 * This method now returns an ArrayList containing a subclass of Fruit.
	 * Notice that only Apples and subclasses of apples are allowed, therefore
	 * Fruit and Pear are no longer valid.
	 * @return
	 */
	public ArrayList<Apple> getFruits3() {
		ArrayList<Apple> apples = new ArrayList<Apple>();
		apples.add(new Apple());
		apples.add(new Braeburn());
		//The next four lines are commented as they are not valid.
		//apples.add(new Pear());
		//apples.add(new Fruit());
		//apples.add(new Computer());
		//apples.add(new BananaCake());
		return apples;
	}
	
	/**
	 * This method now includes all EatableThing objects.
	 * As EatableThing is an interface and it is implemented by Fruit (and its subclasses) 
	 * as well as BananaCake, all those classes except Computer are valid.
	 * @return
	 */
	public ArrayList<EatableThing> getFruits4() {
		ArrayList<EatableThing> things = new ArrayList<EatableThing>();
		things.add(new BananaCake());
		things.add(new Fruit());
		things.add(new Braeburn());
		//things.add(new Computer());
		things.add(new Apple());
		things.add(new Pear());
		return things;
	}
	
	public static void main(String[] args) {
		Generics g = new Generics();
		
		System.out.println("--getFruits1--");
		//Play with list returned from getFruits1
		//Notice that if the method does not return a collection using Generics 
		//you can still force it to:
		ArrayList<Fruit> f1 = g.getFruits1();
		//However, when you loop through the list assuming they are all fruits, you will get an error
		//for (Fruit f : f1) {
		//	System.out.println(f);
		//}
		//However, this is ok (although you will no longer know what object type it is, so you
		//can't do anything useful with it:
		for (Object o : f1) {
			System.out.print(o);
			//Of course, you can still do type testing on it, but it looks ugly
			//and performs badly
			if (o instanceof Fruit) {
				System.out.println(" ...Figured out this is a Fruit");
			} else {
				System.out.println(" ...This is not a fruit");
			}
		}
		
		System.out.println("--getFruits2_1--");
		//Play with list returned from getFruits2_1
		//Although the method does not return a collection using Generics 
		//(you still get the yellow warning) the actual method implementation does use it,
		//so you can safely assume that all objects in the collection are of type Fruit.
		//But if this code was a part of a third-party library where you don't have access
		//to the source code, you can no longer assume this.
		//YOU CAN ONLY TRUST THE METHOD SIGNATURE!
		ArrayList<Fruit> f2_1 = g.getFruits2_1();
		for (Fruit f : f2_1) {
			System.out.println(f);
		}
		
		System.out.println("--getFruits2_2--");
		//Play with list returned from getFruits2_2
		//This is the same as getFruits2_1, except now you can safely assume
		//all objects are of type Fruit, even when you cannot see the source.
		ArrayList<Fruit> f2_2 = g.getFruits2_2();
		for (Fruit f : f2_2) {
			System.out.println(f);
		}
		
		System.out.println("--getFruits3--");
		//Play with list returned from getFruits3
		//You can no longer assign the collection to one of type Fruit.
		//ArrayList<Fruit> f3 = g.getFruits3();
		//This is ok, though:
		ArrayList<Apple> f3 = g.getFruits3();
		for (Apple a : f3) {
			System.out.println(a);
		}
		
		System.out.println("--getFruits4--");
		ArrayList<EatableThing> things = g.getFruits4();
		for (EatableThing e : things) {
			System.out.println(e);
		}
	}
}
